/*
* 数据结构角度：
* 环形队列：当放和拿在同一位置时，无法判断当前队列是空的还是满的
* 处理办法：1.计数器; 2.镂空一个位置
* 
* 生产者消费者角度：
* 生产者和消费者开始的时候，指向同一位置，队列为空，且优先让生产者开始工作
* 生产者和消费者在队列为满的时候，也指向同一位置，且优先让消费者开始工作
* 当队列不为空且不为满的时候，生产者和消费者一定指向不同的位置————生产者消费者可以并发执行
*/
// 设计一个基于环形队列的生产者消费者模型，实现多线程对环形队列的并发访问
#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>

namespace MyRingQueue
{
    const int g_cap_defult = 10;

    template <class T>
    class RingQueue
    {
    private:
        std::vector<T> _ring_queue;
        int _cap;
        // 记录临界资源————空格子的信号量(生产者)
        sem_t _blank_sem;
        // 记录临界资源————数据的信号量(消费者)
        sem_t _data_sem;
        // 记录空格子的位置
        int _c_step;
        // 记录数据的位置
        int _p_step;
        // 加锁，使 生产者之间 和 消费者之间 相互互斥
        pthread_mutex_t _c_mtx;
        pthread_mutex_t _p_mtx;

    public:
        RingQueue(int cap = g_cap_defult)
            : _ring_queue(cap), _cap(cap), _c_step(0), _p_step(0)
        {
            sem_init(&_blank_sem, 0, cap);
            sem_init(&_data_sem, 0, 0);

            pthread_mutex_init(&_c_mtx, nullptr);
            pthread_mutex_init(&_p_mtx, nullptr);
        };

        ~RingQueue()
        {
            sem_destroy(&_blank_sem);
            sem_destroy(&_data_sem);

            pthread_mutex_destroy(&_c_mtx);
            pthread_mutex_destroy(&_p_mtx);
        };

    public:
        void Push(const T &in)
        {
            // 生产者放数据
            
            /*
            pthread_mutex_lock(&_p_mtx);
            1. 如果把锁放在申请信号量之前，那么就是先申请到锁的线程才能申请信号量，这样存在的问题就是多线程往往只有一个线程在工作
            2. 同时如果申请到锁，但是信号量申请失败(信号量为0), 线程抱着锁挂起，直到信号量不为0
            */
            // P()操作————预定空格子资源(P()操作是原子性的，因此不会存在多线程间获取信号量出错)
            sem_wait(&_blank_sem);

            // 多个线程提前获取信号量, 这样可以实现多进程都参与工作, 但是只有一个线程获取到锁
            pthread_mutex_lock(&_p_mtx);
            _ring_queue[_p_step] = in;

            _p_step++;
            _p_step %= _cap;
            pthread_mutex_unlock(&_p_mtx);

            // V()操作————
            sem_post(&_data_sem);
        }

        void Pop(T *out)
        {
            // 消费者拿数据
            // P()操作————预定空格子资源(该操作是原子性的，因此不会存在多线程间获取信号量出错)
            sem_wait(&_data_sem);
            // 消费者都提前获取信号量，但是只有一个线程获取到锁
            pthread_mutex_lock(&_c_mtx);
            *out = _ring_queue[_c_step];

            _c_step++;
            _c_step %= _cap;
            pthread_mutex_unlock(&_c_mtx);

            // V()操作————
            sem_post(&_blank_sem);
        }
    };
}
